package org.apache.ibatis.demo;

import org.apache.ibatis.BaseDataTest;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.domain.blog.Author;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.TypeAliasRegistry;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class SelectOneDemo extends BaseDataTest {
  /*

   */
  static void main0(){
    Configuration configuration = new Configuration();

    SqlSessionFactory sqlMapper = new DefaultSqlSessionFactory(configuration);

    SqlSession session = sqlMapper.openSession();

    Author author = session.selectOne(
      "org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor", new Author(101));

    assertEquals(101, author.getId());
  }

  public static void main(String[] args) {
    try {
      createBlogDataSource();
    } catch (Exception e) {
      e.printStackTrace();
    }
    main1();
  }

  static void main1(){
    Configuration configuration = getConfiguration();

    Environment environment = getEnvironment();
    configuration.setEnvironment(environment);

    MappedStatement mappedStatement = getMappedStatement();
    configuration.addMappedStatement(mappedStatement);

    SqlSessionFactory sqlMapper = new DefaultSqlSessionFactory(configuration);

    SqlSession session = sqlMapper.openSession();
    Author author = session.selectOne(
      "org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor", new Author(101));
    assertEquals(101, author.getId());
  }

  static Configuration configuration = null;

  static Configuration getConfiguration(){
    if(configuration == null){
      configuration = new Configuration();
    }
    return configuration;
  }
  /*
      <select id="selectAuthor" parameterMap="selectAuthor" resultMap="selectAuthor">
        select id, username, password, email, bio, favourite_section
        from author where id = ?
    </select>
   */
  static MappedStatement getMappedStatement(){
    String  id = "org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor";
    boolean isSelect = true;
    String lang = null;
    LanguageDriver langDriver = getLanguageDriver(lang);
    String text = "\n" +
      "        select id, username, password, email, bio, favourite_section\n" +
      "        from author where id = ?\n" +
      "    ";
    SqlNode sqlNode = new StaticTextSqlNode(text);
    MixedSqlNode rootSqlNode = new MixedSqlNode(Arrays.asList(sqlNode));
    SqlSource sqlSource = new RawSqlSource(configuration, rootSqlNode, null);
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;
    String resource = "org/apache/ibatis/builder/AuthorMapper2.xml";

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(getConfiguration(), id, sqlSource, sqlCommandType)
      .resource(resource)
      .fetchSize(null)
      .timeout(null)
      .statementType(StatementType.PREPARED)
      .keyGenerator(NoKeyGenerator.INSTANCE)
      .keyProperty(null)
      .keyColumn(null)
      .databaseId(null)
      .lang(langDriver)
      .resultOrdered(false)
      .resultSets(null)
      .resultMaps(getStatementResultMaps(null, Author.class, id))
      .resultSetType(null)
      .flushCacheRequired(false)
      .useCache(true)
      .cache(null);

    ParameterMap statementParameterMap = getStatementParameterMap("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor", null, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    return statement;
  }


  private static LanguageDriver getLanguageDriver(String lang) {
    Class<? extends LanguageDriver> langClass = null;
    if (lang != null) {
      langClass = resolveClass(lang);
    }
    return configuration.getLanguageDriver(langClass);
  }


  private static List<ResultMap> getStatementResultMaps(
    String resultMap,
    Class<?> resultType,
    String statementId) {
    resultMap = null;

    List<ResultMap> resultMaps = new ArrayList<>();
    if (resultMap != null) {
      String[] resultMapNames = resultMap.split(",");
      for (String resultMapName : resultMapNames) {
        try {
          resultMaps.add(configuration.getResultMap(resultMapName.trim()));
        } catch (IllegalArgumentException e) {
          throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
        }
      }
    } else if (resultType != null) {
      ResultMap inlineResultMap = new ResultMap.Builder(
        configuration,
        statementId + "-Inline",
        resultType,
        new ArrayList<>(),
        null).build();
      resultMaps.add(inlineResultMap);
    }
    return resultMaps;
  }


  private static ParameterMap getStatementParameterMap(
    String parameterMapName,
    Class<?> parameterTypeClass,
    String statementId) {
    parameterMapName = null;
    ParameterMap parameterMap = null;
    if (parameterMapName != null) {
      try {
        parameterMap = configuration.getParameterMap(parameterMapName);
      } catch (IllegalArgumentException e) {
        throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
      }
    } else if (parameterTypeClass != null) {
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      parameterMap = new ParameterMap.Builder(
        configuration,
        statementId + "-Inline",
        parameterTypeClass,
        parameterMappings).build();
    }
    return parameterMap;
  }

  /*
     <environments default="development">
   <environment id="development">
     <transactionManager type="JDBC">
       <property name="" value=""/>
     </transactionManager>
     <dataSource type="UNPOOLED">
       <property name="driver" value="${driver}"/>
       <property name="url" value="${url}"/>
       <property name="username" value="${username}"/>
       <property name="password" value="${password}"/>
     </dataSource>
   </environment>
 </environments>

  if (environment == null) {
        environment = context.getStringAttribute("default");
  }
  for (XNode child : context.getChildren()) {
    String id = child.getStringAttribute("id");
  */
  static Environment getEnvironment(){
    TransactionFactory txFactory = transactionManagerElement();
    DataSourceFactory dsFactory = dataSourceElement();
    DataSource dataSource = dsFactory.getDataSource();
    String id = "development";
    Environment.Builder environmentBuilder = new Environment.Builder(id)
      .transactionFactory(txFactory)
      .dataSource(dataSource);
    return environmentBuilder.build();
  }

  private static TransactionFactory transactionManagerElement( ) {
      String type = "JDBC";
      Properties props = new Properties();
    TransactionFactory factory = null;
    try {
      factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
    factory.setProperties(props);
      return factory;

  }
  /*
  driver=org.apache.derby.jdbc.EmbeddedDriver
  url=jdbc:derby:ibderby;create=true
  username=
  password=
   */
  private static Properties dataSourceProperties(){
    Properties props = new Properties();
    props.put("driver","org.apache.derby.jdbc.EmbeddedDriver");
    props.put("url","jdbc:derby:ibderby;create=true");
    props.put("username","");
    props.put("password","");
    return props;
  }

  private static DataSourceFactory dataSourceElement( ) {
      String type = "UNPOOLED";
      Properties props = dataSourceProperties();
    DataSourceFactory factory = null;
    try {
      factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
    factory.setProperties(props);
    return factory;
  }

  protected static <T> Class<? extends T> resolveClass(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      return resolveAlias(alias);
    } catch (Exception e) {
      throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
  }

  /*
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
   */
  protected static <T> Class<? extends T> resolveAlias(String alias) {
    TypeAliasRegistry typeAliasRegistry = getConfiguration().getTypeAliasRegistry();
    return typeAliasRegistry.resolveAlias(alias);
  }


}
